package com.example.config;

import com.example.service.MyUserDetailService;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    AuthenticationConfiguration authenticationConfiguration;

    private final MyUserDetailService myUserDetailService;

    @Autowired
    public SecurityConfig(MyUserDetailService myUserDetailService) {
        this.myUserDetailService = myUserDetailService;
    }

    //    这里应该是使用自动注入的方式
    @Autowired
    public void initialized(AuthenticationManagerBuilder builder) throws Exception {
//        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
//        userDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
//        builder.userDetailsService(userDetailsManager);

        builder.userDetailsService(myUserDetailService);
    }

    /**
     * 设置加密规则
     * @return 创建的DelegatingPasswordEncoder会自动根据配置的密码存储类型来选择合适的加密规则
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
//        指定加密的方式为bcrypt
        return new BCryptPasswordEncoder();
//        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    // 设置判断登录成功的实现类的bean
    @Bean
    public CustomAuthenticationManager customAuthenticationManager() {
        return new CustomAuthenticationManager();
    }

//    将自定义的filter在工厂中进行注入，实现的是UsernamePasswordAuthenticationFilter
    @Bean
    public LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/doLogin");
        loginFilter.setUsernameParameter("uname");   // 接收json传过来的用户名、密码
        loginFilter.setPasswordParameter("passwd");
        loginFilter.setAuthenticationManager(customAuthenticationManager());  // 如果直接定义数据源使用这行，似乎没有起到作用，这种情况下，使用blr也能登录成功。
//        loginFilter.setAuthenticationManager(authenticationManager(authenticationConfiguration));  // 在userDetail中直接设置用户名，密码{noop}123时，使用改行代码能够认证成功
        loginFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
        loginFilter.setAuthenticationFailureHandler(
//                new AuthenticationFailureHandler() {
//            @Override
//            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//            }
//        }
//                将上述代码更改为匿名内部类的方式去实现
//            (request, response, exception) -> {
//                HashMap<String, Object> result = new HashMap<>();
//                result.put("msg", "登录失败：" + exception.getMessage());
//                result.put("status", 500);
//                response.setContentType("application/json;charset=utf-8");
//                String resultStr = new ObjectMapper().writeValueAsString(result);
//                response.getWriter().println(resultStr);
//            }
                new MyAuthenticationFailureHandler()
        );
        return loginFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, LoginFilter loginFilter) throws Exception {
        httpSecurity
                .authorizeHttpRequests(authorize ->{
                    authorize
                            .requestMatchers("/verifyCode.jpg").permitAll()
                            .anyRequest()
                        .authenticated();  // 所有请求都必须进行认证请求
    //                    .requestMatchers(new AntPathRequestMatcher(
    //                            {"/login", "GET"}
    //                    ))
                    }
                )
                .exceptionHandling(exceptionHandlingConfigurer -> {
                    exceptionHandlingConfigurer.authenticationEntryPoint(((request, response, authException) -> {
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        response.getWriter().println("必须认证以后才能进行访问！");
                    }));
                })
                .formLogin(formLogin -> {
//                    formLogin
//                        .loginProcessingUrl("/login")     // 注意：一旦上面指定了自定义登录页面的url，则必须指定处理登录请求的url，与前端action对应的login相同
//                        .usernameParameter("uname")       // 如果前端自定义的表单，字段不时使用默认的username和password，则这里必须使用参数进行指定
//                        .passwordParameter("passwd");
//            之前的formLogin是从表单请求时，发过来的参数中获取得到username和password
//            前后端分离后，实现从表单中，获取得到各自的用户名和密码

//            at: 用来放在某个filter位置，但并不替换掉过滤链中原filer
//            before: 放在过滤器链中哪个filter之前t
//            after：放在过滤器中哪个filter之后

            })
                .addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class)
//                .authenticationManager()
                .logout(httpSecurityLogoutConfigurer->httpSecurityLogoutConfigurer
                        .logoutSuccessHandler(new MyLogoutSuccessHandler()))
                .csrf(csrf -> {
                    csrf.disable();     // 关闭csrf，否则ajax无法通过拦截
            });
        return httpSecurity.build();
    }
}
